package cn.icanci.loopstack.rec.admin.biz.service.impl;

import cn.hutool.json.JSONUtil;
import cn.icanci.loopstack.rec.admin.biz.mapper.config.StrategyMapper;
import cn.icanci.loopstack.rec.admin.biz.service.StrategyService;
import cn.icanci.loopstack.rec.admin.biz.service.WebApiService;
import cn.icanci.loopstack.rec.common.aggregation.model.*;
import cn.icanci.loopstack.rec.admin.biz.event.log.LogEvent;
import cn.icanci.loopstack.rec.admin.biz.mapper.config.StrategyVoDtoMapper;
import cn.icanci.loopstack.rec.admin.biz.model.StrategyDebugResult;
import cn.icanci.loopstack.rec.admin.dal.mongodb.common.PageList;
import cn.icanci.loopstack.rec.admin.dal.mongodb.daointerface.StrategyDAO;
import cn.icanci.loopstack.rec.admin.dal.mongodb.dateobject.StrategyDO;
import cn.icanci.loopstack.rec.common.enums.DataSourceTypeEnum;
import cn.icanci.loopstack.rec.common.enums.LogOperatorTypeEnum;
import cn.icanci.loopstack.rec.common.enums.ModuleTypeEnum;
import cn.icanci.loopstack.rec.common.enums.ScriptTypeEnum;
import cn.icanci.loopstack.rec.common.model.config.StrategyVO;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngine;
import cn.icanci.loopstack.rec.engine.script.RecScriptEngineManager;
import cn.icanci.loopstack.rec.engine.sdk.actuator.RecRuleEngineActuator;
import cn.icanci.loopstack.rec.engine.sdk.actuator.RuleEngineRequest;
import cn.icanci.loopstack.rec.engine.sdk.actuator.RuleEngineResponse;
import cn.icanci.loopstack.rec.engine.sdk.rule.repository.DomainSceneKey;
import cn.icanci.loopstack.rec.engine.sdk.rule.repository.EngineRepositoryHolder;
import cn.icanci.loopstack.rec.spi.event.EventDispatcher;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.script.CompiledScript;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * @author icanci
 * @since 1.0 Created in 2022/11/12 10:38
 */
@Service
public class StrategyServiceImpl implements StrategyService {
    @Resource
    private StrategyDAO            strategyDAO;
    @Resource
    private StrategyMapper         strategyMapper;
    @Resource
    private StrategyVoDtoMapper    strategyVoDtoMapper;
    @Resource
    private EventDispatcher        eventDispatcher;
    @Resource
    private EngineRepositoryHolder holder;
    @Resource
    private RecRuleEngineActuator  recRuleEngineActuator;
    @Resource
    private WebApiService          webApiService;

    private final RecScriptEngine  recScriptEngine = RecScriptEngineManager.getRecScriptEngine();

    @Override
    public List<StrategyVO> queryAll() {
        return strategyMapper.dos2vos(strategyDAO.queryAll());
    }

    @Override
    public void save(StrategyVO strategy) {
        if (doInsert(strategy)) {
            StrategyDO insert = strategyMapper.vo2do(strategy);
            strategyDAO.insert(insert);
            eventDispatcher.fire(new LogEvent(insert.getUuid(), ModuleTypeEnum.REC_STRATEGY, JSONUtil.toJsonStr(insert), LogOperatorTypeEnum.CREATE));
        } else {
            strategyDAO.update(strategyMapper.vo2do(strategy));
            eventDispatcher.fire(new LogEvent(strategy.getUuid(), ModuleTypeEnum.REC_STRATEGY, JSONUtil.toJsonStr(strategy), LogOperatorTypeEnum.UPDATE));
        }
    }

    @Override
    public StrategyVO queryById(String id) {
        return strategyMapper.do2vo(strategyDAO.queryOneById(id));
    }

    @Override
    public PageList<StrategyVO> queryPage(StrategyVO strategy, int pageNum, int pageSize) {
        PageList<StrategyDO> pageQuery = strategyDAO.pageQuery(strategyMapper.vo2do(strategy), pageNum, pageSize);
        return new PageList<>(strategyMapper.dos2vos(pageQuery.getData()), pageQuery.getPaginator());
    }

    @Override
    public StrategyVO queryByStrategyName(String domainCode, String strategyName) {
        return strategyMapper.do2vo(strategyDAO.queryByStrategyName(domainCode, strategyName));
    }

    @Override
    public StrategyDebugResult debug(StrategyVO strategy, String scriptContentTest) {
        // 执行结果
        StrategyDebugResult result = new StrategyDebugResult();

        String domainCode = strategy.getDomainCode();
        HashSet<String> domainCodes = Sets.newHashSet(domainCode);
        try {
            // 刷新仓储

            // 1.获取domain
            DomainDTO domain = webApiService.loadDomainByDomains(domainCodes).iterator().next();
            // 2.获取基础数据
            List<BaseDataDTO> baseDatas = webApiService.loadBaseDataByDomains(domainCodes);
            compileBaseDataScript(baseDatas);
            Map<String, BaseDataDTO> refreshBaseDatas = baseDatas.stream().collect(Collectors.toMap(BaseDTO::getUuid, baseData -> baseData));
            // 3.构建元数据信息
            List<MetadataDTO> metadatas = webApiService.loadMetadataByDomains(domainCodes);
            Map<String, MetadataDTO> refreshMetadatas = metadatas.stream().collect(Collectors.toMap(BaseDTO::getUuid, metadata -> metadata));
            // 4.构建数据源信息
            List<DataSourceDTO> dataSources = webApiService.loadDataSourceByDomains(domainCodes);
            compileDataSourceScript(dataSources);
            Map<String, DataSourceDTO> refreshDataSources = dataSources.stream().collect(Collectors.toMap(BaseDTO::getUuid, dataSource -> dataSource));
            // 5.构建策略信息
            StrategyDTO strategyDTO = strategyVoDtoMapper.vo2dto(strategy);
            Map<DomainSceneKey, StrategyDTO> refreshStrategies = Lists.newArrayList(strategyDTO).stream()
                .collect(Collectors.toMap(x -> new DomainSceneKey(x.getDomainCode(), x.getSceneCode()), s -> s));

            // 6.全部构建成功之后，刷新缓存
            holder.refresh(domain, refreshBaseDatas, refreshMetadatas, refreshDataSources, refreshStrategies);

            // 7.构建执行参数
            RuleEngineRequest request = new RuleEngineRequest();
            request.setDomainCode(domainCode);
            request.setSceneCode(strategy.getSceneCode());
            request.setParameters(StringUtils.isNotBlank(scriptContentTest) ? JSONUtil.toBean(scriptContentTest, Map.class) : Maps.newHashMap());

            // 8.执行
            RuleEngineResponse execute = recRuleEngineActuator.executor(request);

            // 9.构建执行返回结果
            result.setSuccess(execute.isSuccess());
            result.setRetValue(execute.getResult());
            result.setExecutorParam(JSONUtil.toJsonStr(execute.getBindings()));
            result.setExceptionMessage(execute.isSuccess() ? StringUtils.EMPTY : execute.getErrorMessage());
        } catch (Throwable e) {
            result.setSuccess(false);
            result.setExceptionMessage(e.getMessage());
        } finally {
            // 10.清除仓储
            holder.clear(domainCodes);
        }
        return result;
    }

    @Override
    public StrategyVO queryBySceneCode(String domainCode, String sceneCode) {
        return strategyMapper.do2vo(strategyDAO.queryBySceneCode(domainCode, sceneCode));
    }

    /**
     * 编译执行脚本
     *
     * @param baseDatas baseDatas
     */
    private void compileBaseDataScript(List<BaseDataDTO> baseDatas) {
        if (CollectionUtils.isEmpty(baseDatas)) {
            return;
        }
        for (BaseDataDTO baseData : baseDatas) {
            CompiledScript compiledScript = recScriptEngine.compile(ScriptTypeEnum.valueOf(baseData.getScriptType()), baseData.getScriptContent());
            baseData.setCompiledScript(compiledScript);
        }
    }

    /**
     * 编译执行脚本
     *
     * @param dataSources dataSources
     */
    private void compileDataSourceScript(List<DataSourceDTO> dataSources) {
        if (CollectionUtils.isEmpty(dataSources)) {
            return;
        }
        for (DataSourceDTO dataSource : dataSources) {
            DataSourceTypeEnum sourceType = DataSourceTypeEnum.valueOf(dataSource.getDataSourceType());
            if (sourceType == DataSourceTypeEnum.SCRIPT) {
                DataSourceDTO.ScriptInfo scriptInfo = dataSource.getScriptInfo();
                scriptInfo.setCompiledScript(recScriptEngine.compile(ScriptTypeEnum.valueOf(scriptInfo.getScriptType()), scriptInfo.getScriptContent()));
            }
        }
    }

}
